Android performance par2: Compute
前言
前文主要讲在渲染阶段,我们可以做的优化。本文主要是分析算法对程序运行速度的影响。举个栗子,int的运算速度最快,short次之,byte再次之,long再次之。float和double运算速度最慢;除法比乘法慢的太多,基本上除法是乘法的9倍时间……等等,真的要这么较真吗???我只是举个栗子,这说明,我们的代码,实实在在的在影响程序的运行速度。
当然如果我们遇到的只是一个方法耗时太久,那么问题似乎更简单些,我们只需要优化一个方法函数,而整个的运行速度就会有很大的提高。而更麻烦的就是,我们遇到的是,每个方法都慢了一点点,所以我们花了很大的时间,优化了每个方法,但是整体的速度只提高了一点点。
batching and cashing
性能优化中最常用的一项技术就是,批处理和缓存。这可以减少我们进行耗时运算的次数,从而从整体上降低方法的运行时间。
视频举了个斐波那契的数列1,1,2,3,5,8,13,21,34,……数列从第3项起,每一项是前两项的和,这就是有名的斐波拉契数列。
所以计算斐波那契的数列可以用递归的方式来写:
/**
* Why store things when you can recurse instead? Don't let evidence, personal experience,
* or rational arguments from your peers fool you. The elegant solution is the best solution.
*
* @param positionInFibSequence The position in the fibonacci sequence to return.
* @return the nth number of the fibonacci sequence. Seriously, try to keep up.
*/
public int computeFibonacciRecursive(int positionInFibSequence) {
if (positionInFibSequence <= 2) {
return 1;
} else {
return computeFibonacciRecursive(positionInFibSequence - 1)
+ computeFibonacciRecursive(positionInFibSequence - 2);
}
}
似乎看起来没有问题,但实际运行过程中,我们发现随着数列越来越大,递归算法造成的运行时间成指数级的增长。
我抓了两个,分别是数列35 和 数列37 的计算trace文件
35的时候耗时已经31秒多了,而37时,增加到81秒多。更别提如果是1000的大小的数列。程序的运行时间将达到一个什么级别。
然而在充分理解了函数意义之后,采用算法优化之后,可大大降低运行速度。优化如下:
/**
* It is important to understand what your code is doing, no matter how simple the task. For
* example, most people know better than to compute Fibonacci numbers recursively, but it is
* not unusual to unintentionally redo work in your application. Check your app for places
* where you can cache current results for future re-use.
*
* In this case, recursive Fibonacci calls fib8 which calls fib7 and fib6, but that fib7 call
* calls fib6 again and fib5, So now you've got two fib6's and one fib5 call, but each of those
* fib6 calls will have a fib5 and fib4, so now you have three calls to calculate fib5, blah,
* blah, blah. Recursive fibonacci is terrible. Iterating lets you calculate fibX once,
* use that result twice, and move on.
*
* @param positionInFibSequence The position in the fibonacci sequence to return.
* @return the nth number of the fibonacci sequence. Seriously, try to keep up.
*/
public int computeFibonacci(int positionInFibSequence) {
int prev = 0;
int current = 1;
int newValue;
for (int i=1; i<positionInFibSequence; i++) {
newValue = current + prev;
prev = current;
current = newValue;
}
return current;
}
将之前的值保存起来,这样,不用每一次都递归去重新计算之前的数列值。
再测试下trace运行速度。
计算数列大小为1000的序列值。总共大概三秒。。
这个列子大概演示了,cashing,在程序算法优化上的作用。我们将之前的值cashing起来,而不是再重新计算。大大减少了运算次数。从而将运算性能大大的提高了。
trace view 的使用
找到最耗时的方法,定位性能瓶颈。traceview可以将每个方法的运行时间调用的层级关系清晰的展现出来,方便我们分析。
Android device monitor->devices->package name-> start method profiling-> interact with app ->
使用monitor来获取trace文件,录制了一个动画:
用代码的方式来获取trace文件
- Debug的以下静态方法方法来启动: startMethodTracing(String traceName), stopMethodTracing ()
- adb pull /sdcard/XXXX.trace d:XXX.trace
- 再用DDMS打开trace文件
UI thread
应用启动,系统会创建一个主线程(main thread)。这个主线程负责向UI组件分发事件(包括绘制事件)
- 不要阻塞UI线程。
- 不要在UI线程之外访问Android UI toolkit
如果线程被阻塞了,那么我们可能会得到一个ANR:
应用开发中常见的ANR主要有如下几类:
- 按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);
- 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);
- 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);
发现ANR的时候可以通过如下命令,查看一些anr的信息,ANR文件的分析,我过个阶段会整理下遇到的问题和解决方式。
adb pull /data/anr/ arn/
为了不要阻塞UI线程,我们需要将耗时的工作放在其他线程中。
这个就不具体举例了。
数据结构对性能的影响
我们大概也是举个例子:
ArrayList和LinkedList的大致区别:
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 -对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
这里引入另外一个查看方法执行时间的工作,system trace.
对于想检测的函数方法,我们可以在前后加上代码,然后在抓trace的时候,需要中选应用对应的包名,才可以看到对应的标签。
即上图中的 Trace.begingSection()和 Trace.endSection()
算法优化
本章主要介绍在算法优化上两个我们可以使用作为分析的辅助工具 TraceView和systemtrace。
具体的算法优化方法有批处理,缓存、多线程、数据结构优化,这边只是举了视频中的例子。